package uk.co.g4me.frameworks.spring.roo.addons.roocumber;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.addon.dod.DataOnDemandOperations;
import org.springframework.roo.addon.plural.PluralMetadata;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.FeatureNames;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.DependencyScope;
import org.springframework.roo.project.DependencyType;
import org.springframework.roo.project.Property;
import org.springframework.roo.project.Repository;
import org.springframework.roo.project.maven.Pom;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Element;

/**
 * Implementation of operations this add-on offers.
 *
 * @since 1.1
 */
@Component // Use these Apache Felix annotations to register your commands class in the Roo container
@Service
public class RoocumberOperationsImpl implements RoocumberOperations {
	
	Logger log = HandlerUtils.getLogger(getClass());
    
    /**
     * Use ProjectOperations to install new dependencies, plugins, properties, etc into the project configuration
     */
    @Reference 
    private ProjectOperations projectOperations;

    /**
     * Use TypeLocationService to find types which are annotated with a given annotation in the project
     */
    @Reference 
    private TypeLocationService typeLocationService;
    
    /**
     * Use TypeManagementService to change types
     */
    @Reference 
    private TypeManagementService typeManagementService;
    
    @Reference 
    private DataOnDemandOperations dataOnDemandOperations;
    
    @Reference
	private FileManager fileManager;
    
    @Reference
	private PathResolver pathResolver;
    
    @Reference 
    private MetadataService metadataService;

    private ArrayList<Dependency> dependencies;

	private final String CUCUMBER_VERSION				=	"1.1.2";
	private final String CUCUMBER_CONFIG				=	"cucumber.xml";
	private final String ENTITY_REPLACEMENT_STRING		=	"<entity>";
	private final String ENTITIES_REPLACEMENT_STRING	=	"<entities>";
	private final String FEATURE_TEMPLATE				=	"template.feature";
	private final String TEST_CLASS						=	"RunCukesTest.java";
	private final String PACKAGE_REPLACEMENT_STRING 	= 	"_REPLACE_THIS_STRING_WITH_TARGET_PACKAGE_";
	private final String ENTITY_STEPS_TEMPLATE			=	"EntityStepsTemplate.java";	
	
	private String moduleName;
    
	/* (non-Javadoc)
	 * @see uk.co.g4me.frameworks.spring.roo.addons.roocumber.RoocumberOperations#isSetupCommandAvailable()
	 */
    @Override
	public boolean isSetupCommandAvailable() {
        // Check if a project has been created
        return projectOperations.isFocusedProjectAvailable() && lacksDependency("cucumber-java");
    }
    
	/* (non-Javadoc)
	 * @see uk.co.g4me.frameworks.spring.roo.addons.roocumber.RoocumberOperations#isEntityJPACommandAvailable()
	 */
	@Override
	public boolean isEntityJPACommandAvailable() {
    	return projectOperations.isFocusedProjectAvailable()
                && projectOperations.isFeatureInstalledInFocusedModule(
                        FeatureNames.JPA);
	}

	/* (non-Javadoc)
	 * @see uk.co.g4me.frameworks.spring.roo.addons.roocumber.RoocumberOperations#isEntityMongoCommandAvailable()
	 */
	@Override
	public boolean isEntityMongoCommandAvailable() {
		return projectOperations.isFocusedProjectAvailable()
                && projectOperations.isFeatureInstalledInFocusedModule(
                        FeatureNames.MONGO);
	}
	
	private boolean lacksDependency(String artifactId) {
		Pom pom = projectOperations.getFocusedModule();
		for (Dependency dependency : pom.getDependencies()) {
			if (artifactId.toLowerCase().equals(dependency.getArtifactId().toLowerCase())) {
				return false;
			} else {
				continue;
			}
		}
		return true;
	}
    
    /* (non-Javadoc)
	 * @see uk.co.g4me.frameworks.spring.roo.addons.roocumber.RoocumberOperations#setup()
	 */
    @Override
	public void setup() {
    	
    	moduleName = projectOperations.getFocusedModuleName();
    	
    	//addRepositories();
    	
    	addDependencies();
    	
    	removeDependencies();
    	
    	addConfigXml();
    	
    	addTestClass();
        
    }
    
    

	private void addTestClass() {    	
    	String destination = pathResolver.getFocusedIdentifier(Path.SRC_TEST_JAVA, projectOperations.getFocusedTopLevelPackage().toString().replace(".", "\\") + "\\" + TEST_CLASS);		
		Map<String, String> fileMutations = new HashMap<String, String>();		
    	fileMutations.put(PACKAGE_REPLACEMENT_STRING, projectOperations.getFocusedTopLevelPackage().toString());				
		copyMutableFile(TEST_CLASS, destination, fileMutations);
	}
	
	@Override
	public void setupAllEntityJPA() {
		for (JavaType type: typeLocationService.findTypesWithAnnotation(new JavaType("org.springframework.roo.addon.javabean.RooJavaBean"))) {
            setupEntityJPA(type);
        }
	}

	/* (non-Javadoc)
	 * @see uk.co.g4me.frameworks.spring.roo.addons.roocumber.RoocumberOperations#setupEntityJPA(org.springframework.roo.model.JavaType)
	 */
	@Override
	public void setupEntityJPA(JavaType entity) {
    	
    	copyFeatureFile(entity);
    	
    	createEntityDataOnDemand(entity);
    	
    	createEntityTestSteps(entity);
						
	}

	private void createEntityTestSteps(JavaType entity) {
		String destination = pathResolver.getFocusedIdentifier(Path.SRC_TEST_JAVA, entity.getPackage().toString().replace(".", "\\") + "\\" + entity.getSimpleTypeName() + "Steps.java");
		Map<String, String> fileMutations = new HashMap<String, String>();		
		
		fileMutations.put(PACKAGE_REPLACEMENT_STRING, entity.getPackage().toString());
		fileMutations.put("<entity>", entity.getSimpleTypeName());
		fileMutations.put("<entities>", getPlural(entity));		
		
		copyMutableFile(ENTITY_STEPS_TEMPLATE, destination, fileMutations);		
	}

	private void createEntityDataOnDemand(JavaType entity) {
		
		JavaType entityDataOnDemand	=	new JavaType(entity.getFullyQualifiedTypeName() + "DataOnDemand");		
		dataOnDemandOperations.newDod(entity, entityDataOnDemand);
		
	}

	private void copyFeatureFile(JavaType entity) {
    	
    	String entityName	=	entity.getSimpleTypeName();
    	
    	String destination = pathResolver.getFocusedIdentifier(
    							Path.SRC_TEST_RESOURCES, 
    							entity.getPackage().toString().replace(".", "\\") + "\\" + entityName + ".feature");
    	
    	Map<String, String> fileMutations = new HashMap<String, String>();		
		fileMutations.put(ENTITY_REPLACEMENT_STRING, entityName);
		fileMutations.put(ENTITIES_REPLACEMENT_STRING, getPlural(entity));
		
		copyMutableFile(FEATURE_TEMPLATE, destination, fileMutations);
		
	}

	/**
     * Returns the plural form of the given entity
     * 
     * @param javaType the entity for which to get the plural (required)
     * @return <code>null</code> if it can't be found or is actually
     *         <code>null</code>
     */
    private String getPlural(final JavaType javaType) {
        final String key = PluralMetadata.createIdentifier(javaType,
                typeLocationService.getTypePath(javaType));
        final PluralMetadata pluralMetadata = (PluralMetadata) metadataService.get(key);
        if (pluralMetadata == null) {
            // Can't acquire the plural
            return null;
        }
        return pluralMetadata.getPlural();
    }
    
    
   	/**
     * Install the add-on Google code repository needed to get the annotation
     */    
    private void addRepositories() {
    	projectOperations.addRepository(moduleName, new Repository("Roocumber Roo add-on repository", "Roocumber Roo add-on repository", "https://addon-roocumber.googlecode.com/svn/repo"));        	
	}
    
    /**
     * Add required dependencies
     */
    private void addDependencies() {
    	projectOperations.addProperty(moduleName, new Property("cucumber.version", CUCUMBER_VERSION));

    	dependencies = new ArrayList<Dependency>();
    	//dependencies.add(addDependency("uk.co.g4me.frameworks.spring.roo.addons.roocumber", "uk.co.g4me.frameworks.spring.roo.addons.roocumber", PLUGIN_VERSION));
    	dependencies.add(addDependency("info.cukes", "cucumber-junit", "${cucumber.version}"));
    	dependencies.add(addDependency("info.cukes", "cucumber-java", "${cucumber.version}"));
    	dependencies.add(addDependency("info.cukes", "cucumber-spring", "${cucumber.version}"));
    	projectOperations.addDependencies(moduleName, dependencies);
	}
    
    private void removeDependencies() {
    	
    	// Parse the configuration.xml file
        final Element configuration = XmlUtils.getConfiguration(getClass());    	
    	
        final List<Element> junitDependencies = XmlUtils
                .findElements(
                        "/configuration/config[@id = 'junit']/dependencies/dependency",
                        configuration);
        
        List<Dependency> redundantDependencies = new ArrayList<Dependency>();       
        
        for (final Element dependencyElement : junitDependencies) {
			redundantDependencies.add(new Dependency(dependencyElement));
        }
        
    	projectOperations.removeDependencies(moduleName, redundantDependencies);
	}
    
	/* (non-Javadoc)
	 * @see uk.co.g4me.frameworks.spring.roo.addons.roocumber.RoocumberOperations#addConfigXml()
	 */
	@Override
	public void addConfigXml() {
    	String destination = pathResolver.getFocusedIdentifier(Path.SRC_TEST_RESOURCES, CUCUMBER_CONFIG);
		copyFile(CUCUMBER_CONFIG, destination);		
	}
    
    /**
     * @param groupId
     * @param artifactId
     * @param version
     * @return
     */
    private Dependency addDependency(String groupId, String artifactId, String version) {
    	return new Dependency(groupId, artifactId, version, DependencyType.JAR, DependencyScope.PROVIDED);
	}
    
    private void copyFile(String template, String destination) {
    	copyMutableFile(template, destination, new HashMap<String, String>());
    }
    
    private void copyMutableFile(String template, String destination, Map<String, String> fileMutations) {
		//TODO: Refactor into a utilities class
		InputStream in = null;
		OutputStream out = null;
		
		File fileDestination = new File(destination);
		// Create parent directories of they don't exist
		fileDestination.getParentFile().mkdirs();
		
		if (!fileManager.exists(destination)) {
			try {			
				in = FileUtils.getInputStream(getClass(), template);
				out = new FileOutputStream(fileDestination);
				
				if (!fileMutations.isEmpty()) {
					String content = IOUtils.toString(in);
					for (String key : fileMutations.keySet()) {
						content = content.replaceAll(key, fileMutations.get(key));
					}
					in = IOUtils.toInputStream(content);
				}
				
				IOUtils.copy(in, out);				
			}	catch (IOException ioe) {
					throw new IllegalStateException(ioe);
			} finally {
				IOUtils.closeQuietly(in);
				IOUtils.closeQuietly(out);
			}
		}
		
		fileMutations = null;
	}

	
    
}